import type { FunctionArgs } from '@vueuse/core';
import { upperFirst } from 'lodash-es';

export interface ViewportOffsetResult {
   left: number;
   top: number;
   right: number;
   bottom: number;
   rightIncludeBody: number;
   bottomIncludeBody: number;
}

export function getBoundingClientRect(element: Element): DOMRect | number {
   if (!element || !element.getBoundingClientRect) {
      return 0;
   }
   return element.getBoundingClientRect();
}

function trim(string: string) {
   return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
}

/* istanbul ignore next */
export function hasClass(el: Element, cls: string) {
   if (!el || !cls) return false;
   if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
   if (el.classList) {
      return el.classList.contains(cls);
   } else {
      return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
   }
}

/* istanbul ignore next */
export function addClass(el: Element, cls: string) {
   if (!el) return;
   let curClass = el.className;
   const classes = (cls || '').split(' ');

   for (let i = 0, j = classes.length; i < j; i++) {
      const clsName = classes[i];
      if (!clsName) continue;

      if (el.classList) {
         el.classList.add(clsName);
      } else if (!hasClass(el, clsName)) {
         curClass += ' ' + clsName;
      }
   }
   if (!el.classList) {
      el.className = curClass;
   }
}

/* istanbul ignore next */
export function removeClass(el: Element, cls: string) {
   if (!el || !cls) return;
   const classes = cls.split(' ');
   let curClass = ' ' + el.className + ' ';

   for (let i = 0, j = classes.length; i < j; i++) {
      const clsName = classes[i];
      if (!clsName) continue;

      if (el.classList) {
         el.classList.remove(clsName);
      } else if (hasClass(el, clsName)) {
         curClass = curClass.replace(' ' + clsName + ' ', ' ');
      }
   }
   if (!el.classList) {
      el.className = trim(curClass);
   }
}
/**
 * Get the left and top offset of the current element
 * left: the distance between the leftmost element and the left side of the document
 * top: the distance from the top of the element to the top of the document
 * right: the distance from the far right of the element to the right of the document
 * bottom: the distance from the bottom of the element to the bottom of the document
 * rightIncludeBody: the distance between the leftmost element and the right side of the document
 * bottomIncludeBody: the distance from the bottom of the element to the bottom of the document
 *
 * @description:
 */
export function getViewportOffset(element: Element): ViewportOffsetResult {
   const doc = document.documentElement;

   const docScrollLeft = doc.scrollLeft;
   const docScrollTop = doc.scrollTop;
   const docClientLeft = doc.clientLeft;
   const docClientTop = doc.clientTop;

   const pageXOffset = window.pageXOffset;
   const pageYOffset = window.pageYOffset;

   const box = getBoundingClientRect(element);

   const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect;

   const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0);
   const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0);
   const offsetLeft = retLeft + pageXOffset;
   const offsetTop = rectTop + pageYOffset;

   const left = offsetLeft - scrollLeft;
   const top = offsetTop - scrollTop;

   const clientWidth = window.document.documentElement.clientWidth;
   const clientHeight = window.document.documentElement.clientHeight;
   return {
      left: left,
      top: top,
      right: clientWidth - rectWidth - left,
      bottom: clientHeight - rectHeight - top,
      rightIncludeBody: clientWidth - left,
      bottomIncludeBody: clientHeight - top,
   };
}

export function hackCss(attr: string, value: string) {
   const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT'];

   const styleObj: any = {};
   prefix.forEach((item) => {
      styleObj[`${item}${upperFirst(attr)}`] = value;
   });
   return {
      ...styleObj,
      [attr]: value,
   };
}

/* istanbul ignore next */
// 添加监听
export function on(
   element: Element | HTMLElement | Document | Window,
   event: string,
   handler: EventListenerOrEventListenerObject,
): void {
   if (element && event && handler) {
      element.addEventListener(event, handler, false);
   }
}

/* istanbul ignore next */
// 删除监听
export function off(
   element: Element | HTMLElement | Document | Window,
   event: string,
   handler: Fn,
): void {
   if (element && event && handler) {
      element.removeEventListener(event, handler, false);
   }
}

/* istanbul ignore next */
export function once(el: HTMLElement, event: string, fn: EventListener): void {
   const listener = function (this: any, ...args: unknown[]) {
      if (fn) {
         fn.apply(this, args);
      }
      off(el, event, listener);
   };
   on(el, event, listener);
}

export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
   let locked = false;
   // @ts-ignore
   return function (...args: any[]) {
      if (locked) return;
      locked = true;
      window.requestAnimationFrame(() => {
         // @ts-ignore
         fn.apply(this, args);
         locked = false;
      });
   };
}
